﻿using System;
using System.Collections.Generic;
using System.Text;
using RoughJs.Ast;
using System.Xml;
using RoughJs.Helper;

namespace RoughJs.E4X
{
    public class XmlNode
    {
        private static String XML_NAMESPACES_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";

        private static String USER_DATA_XMLNODE_KEY = typeof(XmlNode).Name;

        private static bool DOM_LEVEL_3 = true;

        public class InternalList
        {

            private List<XmlNode> list;

            public InternalList()
            {
                list = new List<XmlNode>();
            }

            private void _add(XmlNode n)
            {
                list.Add(n);
            }

            public XmlNode item(int index)
            {
                return list[index];
            }

            public void remove(int index)
            {
                list.RemoveAt(index);
            }

            public void add(InternalList other)
            {
                for (int i = 0; i < other.length(); i++)
                {
                    _add(other.item(i));
                }
            }

            public void add(InternalList from, int startInclusive, int endExclusive)
            {
                for (int i = startInclusive; i < endExclusive; i++)
                {
                    _add(from.item(i));
                }
            }

            public void add(XmlNode node)
            {
                _add(node);
            }

            /* TODO: was marked deprecated by original author */
            public void add(XML xml)
            {
                _add(xml.getAnnotation());
            }

            /* TODO: was marked deprecated by original author */
            public void addToList(Object toAdd)
            {
                if (toAdd is Undefined)
                {
                    // Missing argument do nothing...
                    return;
                }

                if (toAdd is XMLList)
                {
                    XMLList xmlSrc = (XMLList)toAdd;
                    for (int i = 0; i < xmlSrc.length(); i++)
                    {
                        this._add((xmlSrc.item(i)).getAnnotation());
                    }
                }
                else if (toAdd is XML)
                {
                    this._add(((XML)(toAdd)).getAnnotation());
                }
                else if (toAdd is XmlNode)
                {
                    this._add((XmlNode)toAdd);
                }
            }

            public int length()
            {
                return list.Count;
            }
        }



        public class Namespace
        {


            public static Namespace create(String prefix, String uri)
            {
                if (prefix == null) throw new ArgumentException("Empty string represents default namespace prefix");
                if (uri == null) throw new ArgumentException("Namespace may not lack a URI");
                Namespace rv = new Namespace();
                rv.prefix = prefix;
                rv.uri = uri;
                return rv;
            }

            public static Namespace create(String uri)
            {
                Namespace rv = new Namespace();
                rv.uri = uri;
                return rv;
            }

            public static Namespace GLOBAL = create("", "");

            public String prefix;
            public String uri;

            private Namespace()
            {
            }


            public override String ToString()
            {
                if (prefix == null) return "XmlNode.Namespace [" + uri + "]";
                return "XmlNode.Namespace [" + prefix + "{" + uri + "}]";
            }

            public bool isUnspecifiedPrefix()
            {
                return prefix == null;
            }

            public bool is1(Namespace other)
            {
                return this.prefix != null && other.prefix != null && (this.prefix == other.prefix) && (this.uri == other.uri);
            }

            public bool isEmpty()
            {
                return prefix != null && (prefix == "") && (uri == "");
            }

            public bool isDefault()
            {
                return prefix != null && (prefix == "");
            }

            public bool isGlobal()
            {
                return uri != null && (uri == "");
            }


            public void setPrefix(String prefix)
            {
                if (prefix == null) throw new ArgumentException();
                this.prefix = prefix;
            }

            public String getPrefix()
            {
                return prefix;
            }

            public String getUri()
            {
                return uri;
            }
        }



        public class QName
        {


            public static QName create(Namespace namespace1, String localName)
            {

                if (localName != null && (localName == "*")) throw new Exception("* is not valid localName");
                QName rv = new QName();
                rv.namespace1 = namespace1;
                rv.localName = localName;
                return rv;
            }

            /** @deprecated */
            public static QName create(String uri, String localName, String prefix)
            {
                return create(Namespace.create(prefix, uri), localName);
            }

            public static String qualify(String prefix, String localName)
            {
                if (prefix == null) throw new ArgumentException("prefix must not be null");
                if (prefix.Length > 0) return prefix + ":" + localName;
                return localName;
            }

            private Namespace namespace1;
            private String localName;

            private QName()
            {
            }


            public override String ToString()
            {
                return "XmlNode.QName [" + localName + "," + namespace1 + "]";
            }

            private bool equals(String one, String two)
            {
                if (one == null && two == null) return true;
                if (one == null || two == null) return false;
                return one == two;
            }

            private bool namespacesEqual(Namespace one, Namespace two)
            {
                if (one == null && two == null) return true;
                if (one == null || two == null) return false;
                return equals(one.getUri(), two.getUri());
            }

            public bool isEqualTo(QName other)
            {
                if (!namespacesEqual(this.namespace1, other.namespace1)) return false;
                if (!equals(this.localName, other.localName)) return false;
                return true;
            }

            public void lookupPrefix(System.Xml.XmlNode node)
            {
                if (node == null) throw new ArgumentException("node must not be null");

                String prefix = node.GetPrefixOfNamespace(namespace1.getUri());
                if (prefix == null)
                {
                    //    check to see if we match the default namespace
                    String defaultNamespace = node.GetPrefixOfNamespace(null);
                    if (defaultNamespace == null) defaultNamespace = "";
                    String nodeNamespace = namespace1.getUri();
                    if (nodeNamespace == defaultNamespace)
                    {
                        prefix = "";
                    }
                }
                int i = 0;
                while (prefix == null)
                {
                    String generatedPrefix = "e4x_" + i++;
                    String generatedUri = node.GetPrefixOfNamespace(generatedPrefix);
                    if (generatedUri == null)
                    {
                        prefix = generatedPrefix;
                        System.Xml.XmlNode top = node;
                        while (top.ParentNode != null && top.ParentNode is XmlElement)
                        {
                            top = top.ParentNode;
                        }
                        ((XmlElement)top).SetAttribute("xmlns:" + prefix, "http://www.w3.org/2000/xmlns/", namespace1.getUri());
                    }
                }
                namespace1.setPrefix(prefix);
            }

            public String qualify(System.Xml.XmlNode node)
            {
                if (namespace1.getPrefix() == null)
                {
                    if (node != null)
                    {
                        lookupPrefix(node);
                    }
                    else
                    {
                        if (namespace1.getUri() == "")
                        {
                            namespace1.setPrefix("");
                        }
                        else
                        {
                            //    TODO    I am not sure this is right, but if we are creating a standalone node, I think we can set the
                            //            default namespace on the node itself and not worry about setting a prefix for that namespace.
                            namespace1.setPrefix("");
                        }
                    }
                }
                return qualify(namespace1.getPrefix(), localName);
            }

            public void setAttribute(XmlElement element, String value)
            {
                if (namespace1.getPrefix() == null) lookupPrefix(element);
                element.SetAttribute(qualify(namespace1.getPrefix(), localName), namespace1.getUri(), value);
            }


            public Namespace getNamespace()
            {
                return namespace1;
            }

            public String getLocalName()
            {
                return localName;
            }
        }


        public class Namespaces
        {
            private IDictionary<String, String> map = new Dictionary<String, String>();
            private IDictionary<String, String> uriToPrefix = new Dictionary<String, String>();

            public Namespaces()
            {
            }

            public void declare(Namespace n)
            {
                if (map.ContainsKey(n.prefix))
                {
                    map[n.prefix] = n.uri;
                }
                //    TODO    I think this is analogous to the other way, but have not really thought it through ... should local scope
                //            matter more than outer scope?
                if (uriToPrefix.ContainsKey(n.uri))
                {
                    uriToPrefix[n.uri] = n.prefix;
                }
            }

            public Namespace getNamespaceByUri(String uri)
            {
                if (uriToPrefix[uri] == null) return null;
                return Namespace.create(uri, uriToPrefix[uri]);
            }

            public Namespace getNamespace(String prefix)
            {
                if (map[prefix] == null) return null;
                return Namespace.create(prefix, map[prefix]);
            }

            public Namespace[] getNamespaces()
            {
                List<Namespace> rv = new List<Namespace>();
                foreach (String prefix in map.Keys)
                {
                    String uri = map[prefix];
                    Namespace n = Namespace.create(prefix, uri);
                    if (!n.isEmpty())
                    {
                        rv.Add(n);
                    }
                }

                return rv.ToArray();
            }
        }

        public class Filter
        {
            public Filter()
            {
            }
            public Filter(XmlNodeType nodeType) { CurrentNode = nodeType; }
            public Filter(XmlNodeType nodeType, XMLName name) { CurrentNode = nodeType; this.name = name; }
            public static Filter COMMENT = new Filter(System.Xml.XmlNodeType.Comment);
            public static Filter TEXT = new Filter(System.Xml.XmlNodeType.Text);
            public static Filter ELEMENT = new Filter(System.Xml.XmlNodeType.Element);

            public static Filter PROCESSING_INSTRUCTION(XMLName name)
            {
                return new Filter(XmlNodeType.ProcessingInstruction, name);
            }

            public static Filter TRUE = new Filter(System.Xml.XmlNodeType.None);
            public bool accept(System.Xml.XmlNode node)
            {
                if (CurrentNode == XmlNodeType.None)
                {
                    return true;
                }
                if (name != null)
                {
                    if (node.NodeType == CurrentNode)
                    {
                        XmlProcessingInstruction pi = (XmlProcessingInstruction)node;
                        return name.matchesLocalName(pi.Target);
                    }
                    else
                    {
                        return false;
                    }
                }

                return node.NodeType == CurrentNode;
            }
            private XMLName name = null;
            public System.Xml.XmlNodeType CurrentNode;

        }


        //    public class XmlNodeUserDataHandler : UserDataHandler{


        //    public void handle(short operation, String key, Object data, Node src, Node dest) {
        //    }
        //}



        //private static XmlNode getUserData(System.Xml.XmlNode node)
        //{
        //    if (DOM_LEVEL_3)
        //    {
        //        return (XmlNode)node.get(USER_DATA_XMLNODE_KEY);
        //    }
        //    return null;
        //}

        //private static void setUserData(Node node, XmlNode wrap)
        //{
        //    if (DOM_LEVEL_3)
        //    {
        //        node.setUserData(USER_DATA_XMLNODE_KEY, wrap, wrap.events);
        //    }
        //}


        public static XmlNode createEmpty(XmlProcessor processor)
        {
            return createText(processor, "");
        }
        /// <summary>
        /// 创建文本节点
        /// </summary>
        /// <param name="processor"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static XmlNode createText(XmlProcessor processor, String value)
        {
            return createImpl(processor.newDocument().CreateTextNode(value));
        }
        public static XmlNode createElement(XmlProcessor processor, String namespaceUri, String xml)
        {
            return createImpl(processor.toXml(namespaceUri, xml));
        }
        private static XmlNode createImpl(System.Xml.XmlNode node)
        {
            if (node is XmlDocument) throw new ArgumentException();
            XmlNode rv = null;
            //if (getUserData(node) == null) {
            rv = new XmlNode();
            rv.dom = node;
            //   // setUserData(node, rv);
            //} else {
            //   // rv = getUserData(node);
            //}
            // node.
            return rv;
        }

        public static XmlNode newElementWithText(XmlProcessor processor, XmlNode reference, XmlNode.QName qname, String value)
        {
            //if (reference is XmlDocument) throw new ArgumentException("Cannot use Document node as reference");
            XmlDocument document = null;
            if (reference != null)
            {
                document = reference.dom.OwnerDocument;
            }
            else
            {
                document = processor.newDocument();
            }
            System.Xml.XmlNode referenceDom = (reference != null) ? reference.dom : null;
            Namespace ns = qname.getNamespace();
            XmlElement e = (ns == null || ns.getUri().Length == 0)
                ? document.CreateElement(qname.getLocalName(), null)
                : document.CreateElement(qname.qualify(referenceDom), ns.getUri());
            if (value != null)
            {
                e.AppendChild(document.CreateTextNode(value));
            }
            return XmlNode.createImpl(e);
        }

        public static XmlNode createElementFromNode(System.Xml.XmlNode node)
        {
            if (node is XmlDocument)
                node = ((XmlDocument)node).DocumentElement;
            return createImpl(node);
        }

        private static XmlNode copy(XmlNode other)
        {
            return createImpl(other.dom.CloneNode(true));
        }


        private System.Xml.XmlNode dom;

        private XML xml;

        public XML getXml()
        {
            return xml;
        }
        public void setXml(XML xml)
        {
            this.xml = xml;
        }
        public int getChildCount()
        {
            return this.dom.ChildNodes.Count;
        }

        public XmlNode parent()
        {
            System.Xml.XmlNode domParent = dom.ParentNode;
            if (domParent is XmlDocument) return null;
            if (domParent == null) return null;
            return createImpl(domParent);
        }
        public void replaceWith(XmlNode other)
        {
            System.Xml.XmlNode replacement = other.dom;
            if (replacement.OwnerDocument != this.dom.OwnerDocument)
            {
                replacement = this.dom.OwnerDocument.ImportNode(replacement, true);
            }
            this.dom.ParentNode.ReplaceChild(replacement, this.dom);
        }
        //public String getAttributeValue()
        //{
        //    return ((XmlAttribute)dom).Value;
        //}

        public int getChildIndex()
        {
            if (this.isAttributeType()) return -1;
            if (parent() == null) return -1;
            XmlNodeList siblings = this.dom.ParentNode.ChildNodes;
            for (int i = 0; i < siblings.Count; i++)
            {
                if (siblings[i] == dom)
                {
                    return i;
                }
            }
            //    Either the parent is -1 or one of the this node's parent's children is this node.
            throw new Exception("Unreachable.");
        }

        public void removeChild(int index)
        {
            this.dom.RemoveChild(this.dom.ChildNodes[index]);
        }

        public String toXmlString(XmlProcessor processor)
        {
            return processor.ecmaToXmlString(this.dom);
        }

        public String ecmaValue()
        {
            //    TODO    See ECMA 357 Section 9.1
            if (isTextType())
            {
                return ((XmlText)dom).Value;
            }
            else if (isAttributeType())
            {
                return ((XmlAttribute)dom).Value;
            }
            else if (isProcessingInstructionType())
            {
                return ((XmlProcessingInstruction)dom).Value;
            }
            else if (isCommentType())
            {
                return ((XmlComment)dom).Value;
            }
            else if (isElementType())
            {
                throw new Exception("Unimplemented ecmaValue() for elements.");
            }
            else
            {
                throw new Exception("Unimplemented for node " + dom);
            }
        }

        public void deleteMe()
        {
            if (dom is XmlAttribute)
            {
                XmlAttribute attr = (XmlAttribute)this.dom;
                attr.OwnerElement.Attributes.RemoveNamedItem(attr.LocalName, attr.NamespaceURI);
            }
            else
            {
                if (this.dom.ParentNode != null)
                {
                    this.dom.ParentNode.RemoveChild(this.dom);
                }
                else
                {
                    //    This case can be exercised at least when executing the regression
                    //    tests under https://bugzilla.mozilla.org/show_bug.cgi?id=354145
                }
            }
        }

        public void normalize()
        {
            this.dom.Normalize();
        }

        public void insertChildAt(int index, XmlNode node)
        {
            System.Xml.XmlNode parent = this.dom;
            System.Xml.XmlNode child = parent.OwnerDocument.ImportNode(node.dom, true);
            if (parent.ChildNodes.Count < index)
            {
                //    TODO    Check ECMA for what happens here
                throw new ArgumentException("index=" + index + " length=" + parent.ChildNodes.Count);
            }
            if (parent.ChildNodes.Count == index)
            {
                parent.AppendChild(child);
            }
            else
            {
                parent.InsertBefore(child, parent.ChildNodes[index]);
            }
        }

        public void insertChildrenAt(int index, XmlNode[] nodes)
        {
            for (int i = 0; i < nodes.Length; i++)
            {
                insertChildAt(index + i, nodes[i]);
            }
        }

        public XmlNode getChild(int index)
        {
            System.Xml.XmlNode child = dom.ChildNodes[index];
            return createImpl(child);
        }

        //    Helper method for XML.hasSimpleContent()
        public bool hasChildElement()
        {
            System.Xml.XmlNodeList nodes = this.dom.ChildNodes;
            for (int i = 0; i < nodes.Count; i++)
            {
                if (nodes[i].NodeType == XmlNodeType.Element) return true;
            }
            return false;
        }

        public bool isSameNode(XmlNode other)
        {
            //    TODO    May need to be changed if we allow XmlNode to refer to several Node objects
            return this.dom == other.dom;
        }

        private String toUri(String ns)
        {
            return (ns == null) ? "" : ns;
        }

        private void addNamespaces(Namespaces rv, XmlElement element)
        {
            if (element == null) throw new Exception("element must not be null");
            String myDefaultNamespace = toUri(element.GetNamespaceOfPrefix(""));
            String parentDefaultNamespace = "";
            if (element.ParentNode != null)
            {
                parentDefaultNamespace = toUri(element.ParentNode.GetNamespaceOfPrefix(""));
            }
            if (!(myDefaultNamespace == parentDefaultNamespace) || !(element.ParentNode is XmlElement))
            {
                rv.declare(Namespace.create("", myDefaultNamespace));
            }
            XmlAttributeCollection attributes = element.Attributes;
            for (int i = 0; i < attributes.Count; i++)
            {
                XmlAttribute attr = (XmlAttribute)attributes[i];
                if (attr.Prefix != null && (attr.Prefix == "xmlns"))
                {
                    rv.declare(Namespace.create(attr.LocalName, attr.Value));
                }
            }
        }

        private Namespaces getAllNamespaces()
        {
            Namespaces rv = new Namespaces();

            System.Xml.XmlNode target = this.dom;
            if (target is XmlAttribute)
            {
                target = ((XmlAttribute)target).OwnerElement;
            }
            while (target != null)
            {
                if (target is XmlElement)
                {
                    addNamespaces(rv, (XmlElement)target);
                }
                target = target.ParentNode;
            }
            //    Fallback in case no namespace was declared
            rv.declare(Namespace.create("", ""));
            return rv;
        }

        public Namespace[] getInScopeNamespaces()
        {
            Namespaces rv = getAllNamespaces();
            return rv.getNamespaces();
        }

        public Namespace[] getNamespaceDeclarations()
        {
            //    ECMA357 13.4.4.24
            if (this.dom is XmlElement)
            {
                Namespaces rv = new Namespaces();
                addNamespaces(rv, (XmlElement)this.dom);
                return rv.getNamespaces();
            }
            else
            {
                return new Namespace[0];
            }
        }

        public Namespace getNamespaceDeclaration(String prefix)
        {
            if (prefix == "" && dom is XmlAttribute)
            {
                //    Default namespaces do not apply to attributes; see XML Namespaces section 5.2
                return Namespace.create("", "");
            }
            Namespaces rv = getAllNamespaces();
            return rv.getNamespace(prefix);
        }
        public Namespace getNamespaceDeclaration()
        {
            if (dom.Prefix == null) return getNamespaceDeclaration("");
            return getNamespaceDeclaration(dom.Prefix);
        }




        public XmlNode copy()
        {
            return copy(this);
        }

        //    Returns whether this node is capable of being a parent
        public bool isParentType()
        {
            return isElementType();
        }

        public bool isTextType()
        {
            return dom.NodeType == XmlNodeType.Text || dom.NodeType == XmlNodeType.CDATA;
        }

        public bool isAttributeType()
        {
            return dom.NodeType == XmlNodeType.Attribute;
        }

        public bool isProcessingInstructionType()
        {
            return dom.NodeType == XmlNodeType.ProcessingInstruction;
        }

        public bool isCommentType()
        {
            return dom.NodeType == XmlNodeType.Comment;
        }

        public bool isElementType()
        {
            return dom.NodeType == XmlNodeType.Element;
        }

        public void renameNode(QName qname) {
            XmlHelper.RenameElement(dom, dom.Name, qname.qualify(dom), qname.getNamespace().getUri());
        //this.dom = dom.OwnerDocument.(dom, , );
    }

        public void invalidateNamespacePrefix()
        {
            if (!(dom is XmlElement)) throw new Exception();
            String prefix = this.dom.Prefix;
            QName after = QName.create(this.dom.NamespaceURI, this.dom.LocalName, null);
            renameNode(after);
            XmlAttributeCollection attrs = this.dom.Attributes;
            for (int i = 0; i < attrs.Count; i++)
            {
                if (attrs[i].Prefix == prefix)
                {
                    createImpl(attrs[i]).renameNode(QName.create(attrs[i].NamespaceURI, attrs[i].LocalName, null));
                }
            }
        }

        private void declareNamespace(XmlElement e, String prefix, String uri)
        {
            if (prefix.Length > 0)
            {
                e.SetAttribute(XML_NAMESPACES_NAMESPACE_URI, "xmlns:" + prefix, uri);
            }
            else
            {
                e.SetAttribute("xmlns", uri);
            }
        }

        public void declareNamespace(String prefix, String uri) {
        if (!(dom is XmlElement)) throw new Exception();
        if (dom.GetNamespaceOfPrefix("") != null && dom.GetNamespaceOfPrefix(uri)==prefix) {
            //    do nothing
        } else {
            XmlElement e = (XmlElement)dom;
            declareNamespace(e, prefix, uri);
        }
    }

        private Namespace getDefaultNamespace()
        {
            String prefix = "";
            String uri = (dom.GetNamespaceOfPrefix(null) == null) ? "" : dom.GetNamespaceOfPrefix(null);
            return Namespace.create(prefix, uri);
        }

        private String getExistingPrefixFor(Namespace namespace1)
        {
            if (getDefaultNamespace().getUri() == namespace1.getUri())
            {
                return "";
            }
            return dom.GetPrefixOfNamespace(namespace1.getUri());
        }

        private Namespace getNodeNamespace()
        {
            String uri = dom.NamespaceURI;
            String prefix = dom.Prefix;
            if (uri == null) uri = "";
            if (prefix == null) prefix = "";
            return Namespace.create(prefix, uri);
        }

        public Namespace getNamespace()
        {
            return getNodeNamespace();
        }

        public void removeNamespace(Namespace namespace1)
        {
            Namespace current = getNodeNamespace();

            //    Do not remove in-use namespace
            if (namespace1.is1(current)) return;
            XmlAttributeCollection attrs = this.dom.Attributes;
            for (int i = 0; i < attrs.Count; i++)
            {
                XmlNode attr = XmlNode.createImpl(attrs[i]);
                if (namespace1.is1(attr.getNodeNamespace())) return;
            }

            //    TODO    I must confess I am not sure I understand the spec fully.  See ECMA357 13.4.4.31
            String existingPrefix = getExistingPrefixFor(namespace1);
            if (existingPrefix != null)
            {
                if (namespace1.isUnspecifiedPrefix())
                {
                    //    we should remove any namespace with this URI from scope; we do this by declaring a namespace with the same
                    //    prefix as the existing prefix and setting its URI to the default namespace
                    declareNamespace(existingPrefix, getDefaultNamespace().getUri());
                }
                else
                {
                    if (existingPrefix == namespace1.getPrefix())
                    {
                        declareNamespace(existingPrefix, getDefaultNamespace().getUri());
                    }
                }
            }
            else
            {
                //    the argument namespace is not declared in this scope, so do nothing.
            }
        }

        private void setProcessingInstructionName(String localName)
        {
            XmlProcessingInstruction pi = (XmlProcessingInstruction)this.dom;
            //    We cannot set the node name; Document.renameNode() only supports elements and attributes.  So we replace it
            pi.ParentNode.ReplaceChild(
                pi,
                pi.OwnerDocument.CreateProcessingInstruction(localName, pi.Value)
            );
        }

        public void setLocalName(String localName)
        {
            if (dom is XmlProcessingInstruction)
            {
                setProcessingInstructionName(localName);
            }
            else
            {
                String prefix = dom.Prefix;
                if (prefix == null) prefix = "";
                XmlHelper.RenameElement(dom, dom.Name, QName.qualify(prefix, localName), dom.NamespaceURI);
            }
        }

        public QName getQname()
        {
            String uri = (dom.NamespaceURI) == null ? "" : dom.NamespaceURI;
            String prefix = (dom.Prefix == null) ? "" : dom.Prefix;
            return QName.create(uri, dom.LocalName, prefix);
        }

        public void addMatchingChildren(XMLList result, XmlNode.Filter filter)
        {
            System.Xml.XmlNode node = this.dom;
            XmlNodeList children = node.ChildNodes;
            for (int i = 0; i < children.Count; i++)
            {
                System.Xml.XmlNode childnode = children[i];
                XmlNode child = XmlNode.createImpl(childnode);
                if (filter.accept(childnode))
                {
                    result.addToList(child);
                }
            }
        }

        //private static System.Xml.XmlNode createImpl(System.Xml.XmlNode node)
        //{
        //    if (node is XmlDocument) throw new ArgumentException();
        //    System.Xml.XmlNode rv = null;
        //    //if (getUserData(node) == null)
        //    //{
        //    //    rv = new XmlNode();
        //    //    rv.dom = node;
        //    //    setUserData(node, rv);
        //    //}
        //    //else
        //    //{
        //    //    rv = getUserData(node);
        //    //}
        //    return rv;
        //}

        public XmlNode[] getMatchingChildren(Filter filter)
        {
            List<XmlNode> rv = new List<XmlNode>();
            XmlNodeList nodes = this.dom.ChildNodes;
            for (int i = 0; i < nodes.Count; i++)
            {
                System.Xml.XmlNode node = nodes[i];
                if (filter.accept(node))
                {
                
                    rv.Add(createImpl(node));
                }
            }
            return rv.ToArray();
        }

        public XmlNode[] getAttributes()
        {
            XmlAttributeCollection attrs = this.dom.Attributes;
            //    TODO    Or could make callers handle null?
            if (attrs == null) throw new Exception("Must be element.");
            XmlNode[] rv = new XmlNode[attrs.Count];
            for (int i = 0; i < attrs.Count; i++)
            {
                rv[i] = createImpl(attrs[i]);
            }
            return rv;
        }

        public String getAttributeValue()
        {
            return ((XmlAttribute)dom).Value;
        }

        public void setAttribute(QName name, String value)
        {
            if (!(dom is XmlElement)) throw new Exception("Can only set attribute on elements.");
            name.setAttribute((XmlElement)dom, value);
        }

        //public void replaceWith(XmlNode other)
        //{
        //    System.Xml.XmlNode replacement = other.dom;
        //    if (replacement.OwnerDocument != this.dom.OwnerDocument)
        //    {
        //        replacement = this.dom.OwnerDocument.ImportNode(replacement, true);
        //    }
        //    this.dom.ParentNode.ReplaceChild(replacement, this.dom);
        //}

        public String ecmaToXMLString(XmlProcessor processor)
        {
            if (this.isElementType())
            {
                XmlElement copy = (XmlElement)this.dom.CloneNode(true);
                Namespace[] inScope = this.getInScopeNamespaces();
                for (int i = 0; i < inScope.Length; i++)
                {
                    declareNamespace(copy, inScope[i].getPrefix(), inScope[i].getUri());
                }
                return processor.ecmaToXmlString(copy);
            }
            else
            {
                return processor.ecmaToXmlString(dom);
            }
        }

        public System.Xml.XmlNode toDomNode()
        {
            return this.dom;
        }
    }// end for xmlnode
}
